const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
const url = require('url');

// 创建存储目录
const createDirectory = (dirPath) => {
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath, { recursive: true });
  }
};

// 全局错误日志数组
const errorLogs = [];

// 记录错误日志
const logError = (outputDir, resourceUrl, errorMessage, resourceType) => {
  // 将错误信息添加到全局数组
  errorLogs.push({
    type: resourceType,
    url: resourceUrl,
    error: errorMessage,
    time: new Date().toLocaleString(),
  });

  console.log(`错误已记录: ${resourceType} - ${resourceUrl}`);
  return true;
};

// 下载资源
const downloadResource = async (resourceUrl, savePath) => {
  try {
    const response = await axios({
      method: 'GET',
      url: resourceUrl,
      responseType: 'stream',
      maxRedirects: 5, // 允许最多5次重定向
      timeout: 30000, // 30秒超时
      headers: {
        'User-Agent':
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        Referer: resourceUrl,
      },
    });

    const writer = fs.createWriteStream(savePath);
    response.data.pipe(writer);

    return new Promise((resolve, reject) => {
      writer.on('finish', resolve);
      writer.on('error', (err) => {
        reject(new Error(`写入文件失败: ${err.message}`));
      });
    });
  } catch (error) {
    console.error(`下载资源失败: ${resourceUrl}`, error.message);
    throw error; // 抛出错误，让调用者处理
  }
};

// 爬取网页
const crawlWebsite = async (targetUrl, outputDir) => {
  try {
    // 创建输出目录
    createDirectory(outputDir);

    console.log(`开始爬取: ${targetUrl}`);

    // 获取HTML内容，允许重定向
    const response = await axios.get(targetUrl, {
      maxRedirects: 5, // 允许最多5次重定向
    });

    // 获取最终URL（可能经过重定向）
    const finalUrl = response.request.res.responseUrl || targetUrl;

    if (finalUrl !== targetUrl) {
      console.log(`页面已重定向: ${targetUrl} -> ${finalUrl}`);
    }

    // 解析最终URL
    const parsedUrl = new URL(finalUrl);
    const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`;

    const html = response.data;

    // 保存HTML
    const htmlFileName = path.join(outputDir, 'index.html');
    // 修改HTML中的资源引用路径
    const modifiedHtml = html.replace(
      /(href|src)=["'](?:\.\.\/)*([^"']+)["']/g,
      (match, attr, resourcePath) => {
        // 如果是完整URL或以//开头的协议相对URL，不修改
        if (resourcePath.startsWith('http') || resourcePath.startsWith('//')) {
          return match;
        }

        // 如果路径包含数字目录（如881），移除它
        const pathParts = resourcePath.split('/');
        if (/^\d+$/.test(pathParts[0])) {
          pathParts.shift();
        }

        return `${attr}="${pathParts.join('/')}"`;
      }
    );

    fs.writeFileSync(htmlFileName, modifiedHtml);
    console.log(`HTML已保存至: ${htmlFileName}`);

    // 使用cheerio解析HTML
    const $ = cheerio.load(html);

    // 处理资源URL的函数
    const processResourceUrl = (resourceUrl, shouldDownload = true) => {
      if (!resourceUrl) return { url: null, shouldDownload: false };

      // 检查是否有tppabs属性（特殊情况）
      if (resourceUrl.includes('tppabs=')) {
        const tppabsMatch = resourceUrl.match(/tppabs="([^"]+)"/);
        if (tppabsMatch && tppabsMatch[1]) {
          // tppabs通常指向外部资源，但我们仍然需要下载
          return { url: tppabsMatch[1], shouldDownload: true };
        }
      }

      // 判断是否为相对路径
      const isRelative = !resourceUrl.startsWith('http');

      // 处理相对URL
      if (isRelative) {
        let absoluteUrl;
        if (resourceUrl.startsWith('/')) {
          absoluteUrl = `${baseUrl}${resourceUrl}`;
        } else if (resourceUrl.startsWith('../')) {
          // 处理 ../ 开头的相对路径
          const urlObj = new URL(finalUrl);
          const pathParts = urlObj.pathname.split('/').filter(Boolean);
          const relativeParts = resourceUrl.split('/').filter(Boolean);

          let backCount = 0;
          while (relativeParts.length > 0 && relativeParts[0] === '..') {
            backCount++;
            relativeParts.shift();
          }

          const newPathParts = pathParts.slice(
            0,
            Math.max(0, pathParts.length - backCount)
          );
          const newPath = '/' + [...newPathParts, ...relativeParts].join('/');
          absoluteUrl = `${baseUrl}${newPath}`;
        } else {
          // 处理普通相对路径
          const urlObj = new URL(finalUrl);
          const pathDir = urlObj.pathname.substring(
            0,
            urlObj.pathname.lastIndexOf('/') + 1
          );
          absoluteUrl = `${baseUrl}${pathDir}${resourceUrl}`;
        }
        return { url: absoluteUrl, shouldDownload: true };
      }

      // 对于完整URL，不下载
      return { url: resourceUrl, shouldDownload: false };
    };

    // 获取资源的相对路径
    const getRelativePath = (url) => {
      try {
        const resourceUrl = new URL(url);
        // 移除域名部分，只保留路径
        let pathname = resourceUrl.pathname.startsWith('/')
          ? resourceUrl.pathname.substring(1)
          : resourceUrl.pathname;

        // 解码URL编码的字符
        try {
          pathname = decodeURIComponent(pathname);
        } catch (e) {
          console.log(`URL解码失败: ${pathname}`);
        }

        // 移除URL路径中的数字部分（如881）
        const pathParts = pathname.split('/');
        // 如果第一部分是数字，则移除
        if (/^\d+$/.test(pathParts[0])) {
          pathParts.shift();
        }

        // 分解路径，只替换文件名部分中不合法的字符
        const fileName = pathParts.pop(); // 获取文件名
        const safeFileName = fileName
          ? fileName.replace(/[\\/:*?"<>|]/g, '_')
          : '_index.html';

        // 重新组合路径
        return [...pathParts, safeFileName].join('/');
      } catch (error) {
        // 如果URL解析失败，返回原始URL作为路径
        let safePath = url.replace(/[\\/:*?"<>|]/g, '_');

        // 尝试解码URL编码的字符
        try {
          safePath = decodeURIComponent(safePath);
        } catch (e) {
          console.log(`URL解码失败: ${safePath}`);
        }

        return safePath;
      }
    };

    // 下载资源并保持原始目录结构
    const downloadWithOriginalPath = async (
      url,
      type,
      shouldDownload = true,
      outputDir
    ) => {
      if (!url) return null;

      // 如果不需要下载，直接返回
      if (!shouldDownload) {
        console.log(`跳过下载(完整URL): ${url}`);
        return null;
      }

      // 获取资源的相对路径
      const relativePath = getRelativePath(url);
      // 完整的保存路径
      const savePath = path.join(outputDir, relativePath);
      // 确保目录存在
      createDirectory(path.dirname(savePath));

      console.log(`下载${type}: ${url}`);
      console.log(`保存至: ${savePath}`);

      try {
        await downloadResource(url, savePath);
        return savePath;
      } catch (error) {
        console.error(`下载失败: ${url}`, error.message);
        // 记录下载失败的资源
        logError(outputDir, url, error.message, type);
        return null;
      }
    };

    // 尝试从HTML中提取真实的资源域名
    let alternativeBaseUrl = baseUrl;
    $('link, script, img').each((i, element) => {
      const src = $(element).attr('src') || $(element).attr('href');
      if (src && src.includes('tppabs=')) {
        const tppabsMatch = src.match(/tppabs="([^"]+)"/);
        if (
          tppabsMatch &&
          tppabsMatch[1] &&
          tppabsMatch[1].startsWith('http')
        ) {
          const tppabsUrl = new URL(tppabsMatch[1]);
          alternativeBaseUrl = `${tppabsUrl.protocol}//${tppabsUrl.host}`;
          console.log(`发现可能的真实资源域名: ${alternativeBaseUrl}`);
          return false; // 找到后停止遍历
        }
      }
    });

    // 收集所有资源下载任务
    const downloadPromises = [];

    // 处理CSS文件
    $('link[rel="stylesheet"]').each((i, element) => {
      let cssUrl = $(element).attr('href');
      if (!cssUrl) return;

      const { url: processedUrl, shouldDownload } = processResourceUrl(cssUrl);
      if (!processedUrl) return;

      cssUrl = processedUrl;

      // 如果不需要下载，跳过
      if (!shouldDownload) {
        console.log(`跳过下载(完整URL): ${cssUrl}`);
        return;
      }

      // 尝试使用替代域名
      const alternativeCssUrl = cssUrl.replace(baseUrl, alternativeBaseUrl);

      downloadPromises.push(
        downloadWithOriginalPath(cssUrl, 'CSS', true, outputDir).catch(
          (error) => {
            console.log(`尝试替代URL: ${alternativeCssUrl}`);
            return downloadWithOriginalPath(
              alternativeCssUrl,
              'CSS',
              true,
              outputDir
            ).catch((altError) => {
              // 如果两种URL都失败，记录错误（使用当前时间）
              logError(
                outputDir,
                cssUrl,
                `原始URL和替代URL都下载失败。\n原始错误: ${
                  error ? error.message : '未知错误'
                }\n替代错误: ${altError ? altError.message : '未知错误'}`,
                'CSS'
              );
              return null;
            });
          }
        )
      );
    });

    // 处理JavaScript文件
    $('script').each((i, element) => {
      let jsUrl = $(element).attr('src');
      if (!jsUrl) return;

      const { url: processedUrl, shouldDownload } = processResourceUrl(jsUrl);
      if (!processedUrl) return;

      jsUrl = processedUrl;

      // 如果不需要下载，跳过
      if (!shouldDownload) {
        console.log(`跳过下载(完整URL): ${jsUrl}`);
        return;
      }

      // 尝试使用替代域名
      const alternativeJsUrl = jsUrl.replace(baseUrl, alternativeBaseUrl);

      downloadPromises.push(
        downloadWithOriginalPath(jsUrl, 'JS', true, outputDir).catch(
          (error) => {
            console.log(`尝试替代URL: ${alternativeJsUrl}`);
            return downloadWithOriginalPath(
              alternativeJsUrl,
              'JS',
              true,
              outputDir
            ).catch((altError) => {
              // 如果两种URL都失败，记录错误
              logError(
                outputDir,
                jsUrl,
                `原始URL和替代URL都下载失败。\n原始错误: ${error.message}\n替代错误: ${altError.message}`,
                'JS'
              );
              return null;
            });
          }
        )
      );
    });

    // 处理图片
    $('img').each((i, element) => {
      let imgUrl = $(element).attr('src');
      if (!imgUrl) return;

      const { url: processedUrl, shouldDownload } = processResourceUrl(imgUrl);
      if (!processedUrl) return;

      imgUrl = processedUrl;

      // 如果不需要下载，跳过
      if (!shouldDownload) {
        console.log(`跳过下载(完整URL): ${imgUrl}`);
        return;
      }

      // 尝试使用替代域名
      const alternativeImgUrl = imgUrl.replace(baseUrl, alternativeBaseUrl);

      downloadPromises.push(
        downloadWithOriginalPath(imgUrl, '图片', true, outputDir).catch(
          (error) => {
            console.log(`尝试替代URL: ${alternativeImgUrl}`);
            return downloadWithOriginalPath(
              alternativeImgUrl,
              '图片',
              true,
              outputDir
            ).catch((altError) => {
              // 如果两种URL都失败，记录错误
              logError(
                outputDir,
                imgUrl,
                `原始URL和替代URL都下载失败。\n原始错误: ${error.message}\n替代错误: ${altError.message}`,
                '图片'
              );
              return null;
            });
          }
        )
      );
    });

    // 等待所有资源下载完成
    await Promise.all(downloadPromises);

    console.log(`网站 ${targetUrl} 爬取完成!`);
    return true;
  } catch (error) {
    if (error.response && error.response.status === 404) {
      console.log(`网站 ${targetUrl} 不存在，已跳过`);
      return false;
    }
    console.error(`爬取 ${targetUrl} 时发生错误:`, error.message);
    return false;
  }
};

// 批量爬取网站
const batchCrawl = async (baseUrls, outputBaseDir) => {
  // 创建一个映射来跟踪已使用的标题
  const usedTitles = new Map();

  // 创建与output同级的log文件夹
  const logDir = path.join(path.dirname(outputBaseDir), 'log');
  createDirectory(logDir);

  // 创建本次执行的日志文件名 - 使用简洁的年月日时分秒格式
  const now = new Date();
  const formattedDate = `${now.getFullYear()}${(now.getMonth() + 1)
    .toString()
    .padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}${now
    .getHours()
    .toString()
    .padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now
    .getSeconds()
    .toString()
    .padStart(2, '0')}`;
  const logFileName = path.join(logDir, `${formattedDate}_log.txt`);

  // 记录开始时间
  const startTime = new Date();

  for (const targetUrl of baseUrls) {
    try {
      console.log(`\n===== 处理地址 ${targetUrl} =====`);

      // 先获取网页标题
      const response = await axios
        .get(targetUrl, {
          maxRedirects: 5,
        })
        .catch((error) => {
          console.error(`无法访问 ${targetUrl}: ${error.message}`);
          return null;
        });

      if (!response) {
        console.log(`URL ${targetUrl} 的网站不可访问，已跳过`);
        continue;
      }

      // 解析HTML获取标题
      const $ = cheerio.load(response.data);
      let pageTitle = $('title').text().trim() || 'untitled';
      // 替换不合法的文件名字符
      pageTitle = pageTitle.replace(/[\\/:*?"<>|]/g, '_');

      // 检查标题是否已使用
      if (usedTitles.has(pageTitle)) {
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        pageTitle = `${pageTitle}_${timestamp}`;
        console.log(`标题已存在，使用新标题: ${pageTitle}`);
      } else {
        usedTitles.set(pageTitle, true);
      }

      // 使用标题作为文件夹名
      const outputDir = path.join(outputBaseDir, pageTitle);
      createDirectory(outputDir);

      const success = await crawlWebsite(targetUrl, outputDir);
      if (!success) {
        console.log(`URL ${targetUrl} 的网站不可访问，已创建文件夹并跳过`);
      }
    } catch (error) {
      console.error(`爬取URL ${targetUrl} 时发生错误:`, error.message);
    }

    // 可选：添加延迟，避免请求过于频繁
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }

  // 创建汇总日志
  const logContent = `
爬虫执行日志
开始时间: ${startTime.toLocaleString()}
结束时间: ${new Date().toLocaleString()}
处理URL数量: ${baseUrls.length}
错误数量: ${errorLogs.length}

=== 错误详情 ===
${
  errorLogs.length > 0
    ? errorLogs
        .map(
          (item, index) =>
            `[${index + 1}] 时间: ${item.time}
类型: ${item.type}
URL: ${item.url}
错误: ${item.error}
------------------------------`
        )
        .join('\n')
    : '没有发现错误'
}
`;

  fs.writeFileSync(logFileName, logContent);
  console.log(`\n执行日志已保存至: ${logFileName}`);

  console.log('\n===== 批量爬取完成 =====');
};

// // 命令行参数处理
// const args = process.argv.slice(2);
// if (args.length < 2) {
//   console.log('请提供基础URL和输出目录');
//   console.log('用法: node crawler.js <基础URL> <输出目录> [起始序号]');
//   process.exit(1);
// }

// const baseUrls = new Array(10).fill('http://accct.top/').map((e, i) => e + i);
const baseUrls = [
  'http://accct.top/881',
  'http://accct.top/884',
  'http://accct.top/891',
  'http://accct.top/892',
  'http://wan55.cn/506#6',
  'http://accct.cn/25',
  'http://accct.cn/29',
  'http://accct.cn/50',
  'http://accct.cn/99',
  'http://accct.cn/35',
  'http://accct.cn/52',
  'http://accct.cn/37',
  'http://accct.cn/36',
];
const outputDir = './output';

// 开始批量爬取
batchCrawl(baseUrls, outputDir);
